package jmine.tec.persist.hibernate.proxy;

import java.io.Serializable;
import java.lang.reflect.Method;

import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.ProxyObject;
import jmine.tec.utils.spring.BeanReference;

import org.hibernate.Hibernate;
import org.hibernate.engine.SessionImplementor;

/**
 * Encapsula um objeto "lazy" para fazê-lo nunca perder a sessão possibilitando serialização do mesmo.
 * 
 * @param <T> tipo do objeto "lazy"
 * @author wellington.santos
 * @created May 30, 2012
 */
public abstract class AbstractPersistentProxy<T> implements MethodHandler, Serializable, SerializableLazyProxy<T> {

    private static final long serialVersionUID = -1377418138616283827L;

    private final BeanReference<? extends SessionImplementor> sessionReference;

    private transient ProxyObject proxy;

    /**
     * Construtor.
     *
     * @param sessionReference referência da sessão hibernate
     */
    public AbstractPersistentProxy(BeanReference<? extends SessionImplementor> sessionReference) {
        this.sessionReference = sessionReference;
    }

    /**
     * Restaura a sessão do objeto se ela foi perdida.
     */
    public abstract void restoreSession();

    /**
     * Verifica se o objeto lazy foi inicializado. <br>
     * Caso positivo, este método irá chamar o {@link #onInitialized(Object)}.
     * 
     * @return True se acaba de ser inicializado e {@link #onInitialized(Object)} foi então invocado
     */
    protected abstract boolean checkInitialization();

    /**
     * {@inheritDoc}
     *
     * @return {@inheritDoc}
     */
    public Object invoke(Object theProxy, Method method, Method proceed, Object[] args) throws Throwable {
        if (args.length == 0) {
            String methodName = method.getName();
            if ("writeReplace".equals(methodName)) {
                return this.writeReplace();
            } else if ("isUninitialized".equals(methodName)) {
                return this.isUninitialized();
            } else if ("getImplementation".equals(methodName)) {
                return this.getImplementation();
            } else if ("getLazyObject".equals(methodName)) {
                return this.getLazyObject();
            }
        }
        this.restoreSession();
        Object result = method.invoke(this.getLazyObject(), args);
        if (this.checkInitialization()) {
            Hibernate.initialize(result);
        }
        return result;
    }

    /**
     * {@inheritDoc}
     * 
     * @return {@inheritDoc}
     */
    public final Object writeReplace() {
        if (this.isUninitialized()) {
            return this;
        }
        return this.getImplementation();
    }

    /**
     * @return a proxy que será usada ao deserializar o objeto "lazy" encapsulado
     */
    protected Object readResolve() {
        return this.getProxy();
    }

    /**
     * Obtém a proxy que irá se passar pelo objeto original.
     * 
     * @return a proxy que irá se passar pelo objeto original
     */
    public final Object getProxy() {
        if (this.proxy == null) {
            this.proxy = this.createProxy();
        }
        return this.proxy;
    }

    /**
     * Cria a intância da proxy.
     * 
     * @return a proxy que irá se passar pelo objeto original
     */
    protected abstract ProxyObject createProxy();

    /**
     * @return a sessão do hibernate
     */
    protected SessionImplementor getSessionImplementor() {
        return this.sessionReference.get();
    }

    /**
     * Evento ao efetuar carregamento do objeto "lazy".
     * 
     * @param object objeto já inicializado (não lazy)
     */
    protected abstract void onInitialized(Object object);

}
